Unions in C++

Union: A Special Class Type

在 C++ 中,大多数人可能都知道 classstruct 的类类型,但是许多人会忽略 union 也是一种类类型,这是因为 C++ 中的联合体也可以拥有自己的成员函数(包括构造和析构函数)。但它的使用是特别的,因为其特性,联合体类 union 内每次只能有一个活跃的成员。

由于联合体需要保证类内任何一个成员都能够被访问到,所以联合体类型至少要和类内最大的数据成员的大小一样大,通常等于联合体类内最大的数据成员的大小。当我们访问联合体类时,其类内的每个数据成员都会像是类内唯一的数据成员。

union 的一个简单用例如下,我们用 union 定义了一个联合体类型 U。其中有三个数据成员:

#include <iostream>

union U{ // public access by default
	int u_i; // 4 bytes
	short u_s; // 2 bytes, hold integer number no bigger than 65535
	float u_f; // 4 bytes
}; // thus the size of U class is 4 bytes 

int main(){
	U u = {65536}; // initializes the first member u.u_i
	// U u.u_i = {65536};
	std::cout << u_i << std::endl 
			  << u_s << std::endl 
			  << u_f << std::endl;
	return 0;
}

输出:

65536
0
9.18355e-41

上面的代码中,我们定义了一个包含三个数据成员的 union 类。在 union 中,所有的数据变量都共享一个地址,但是它们访问数据的方式和大小各不相同。我们初始化了第一个成员 u_i,这意味着剩下的两个变量的状态在此时是未定义的。这也就不难理解为什么我们会得到如此奇怪的输出。

在一时刻, union 的内存布局如下所示:
member_union.png
因为 short 类型大小为两个字节,所以即使 shortint 都表示整型数,在赋值 65536 时,short 仍然不能访问高二字节地址的数据。而 float 数据的访问方式又与整型数有所不同,我们会得到不一样的结果。如果后面我们对 u_s 进行赋值,那么 u_i 的生命周期也就随之结束。

以上是联合体最常用的方式。这样做有什么好处?节省内存。

Some Rules to Obey in Union Class

作为一种特殊的类, union 可以拥有自己的成员函数。但需要注意的是 union 不允许继承和派生,因此也不允许出现虚函数。union 中,拷贝构造函数、移动构造函数、拷贝赋值运算符、移动赋值运算符和析构函数默认会被删除。除非显式地定义这些函数。

Anonymous Unions

匿名联合体是匿名的,它不能有任何成员函数、静态数据成员而且所有的数据成员都需要是公有的。匿名联合体必须定义在作用域 (scope) 中,而且其成员不能与作用域中已声明的名字冲突。接着上面的例子,它的匿名联合体会是这样的:

#include <iostream>
namespace U {
    long u_l;
    union { // public access by default
        int u_i; // 4 bytes
        short u_s; // 2 bytes, holds integer numbers no bigger than 65535
        float u_f; // 4 bytes
	    // long u_l; // not allowed
    } static; // anonymous unions at namespace scope must be static
}

int main() {
    U::u_i = 65536; // initializes the first member myspace::u_i
    std::cout << U::u_i << std::endl 
              << U::u_s << std::endl 
              << U::u_f << std::endl;
    return 0;
}

如果在一个函数的作用域中,匿名函数就是这样的:

#include <iostream>

int main() {
	union { // public access by default
        int u_i; // 4 bytes
        short u_s; // 2 bytes, holds integer numbers no bigger than 65535
        float u_f; // 4 bytes
    };
    u_i = 65536; // initializes u_i
    std::cout << u_i << std::endl 
              << u_s << std::endl 
              << u_f << std::endl;
    return 0;
}

Union-like Classes